// Main.cpp
//
// Copyright (c) 2008 Bojan Rajkovic
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//

#include "../include/AllPurpose.h"
#include <signal.h>
#include "../include/Keycounter.h"
#include "../include/CommandInterface.h"
#include <boost/thread.hpp>
#include <boost/bind.hpp>

using namespace LinPulse;

LinPulse::Keycounter* keycounter;
LinPulse::CommandInterface* commandInterface;

void terminate(int parameter) {
	Utility::LogMessage(_("Caught a sigterm will shut down now."), M_INFO);
	Pulse::SavePulseFile(keycounter->Keys());
	delete commandInterface;
	exit(EXIT_SUCCESS);
}

void interrupt(int parameter) {
	Utility::LogMessage(_("Caught a sigint, will shut down now."), M_INFO);
	Pulse::SavePulseFile(keycounter->Keys());
	delete commandInterface;
	exit(EXIT_SUCCESS);
}

void hangup(int parameter) {
	keycounter->Pulse();
}

void Daemonize() {
	Utility::LogMessage(_("Daemonizing keycounter."), M_INFO);
		
		// Fork the parent process
		pid_t pid;
		pid = fork();

		// Write the child's PID to the log and to the pid file so we can kill the daemon.
		ofstream pidFile ("/var/run/keycounter.pid", ios_base::trunc);
		if (!pidFile.good()) {
			Utility::LogMessage(Utility::FormatString(_("Could not open PID file for writing: %s"), strerror(errno)), 
				M_ERROR);
			exit(EXIT_FAILURE);
		}
		if (pid != 0) { 
			pidFile << pid << endl;
			// log the child pid and write it out to the pid file
			// so we can kill the daemon from outside
			Utility::LogMessage(Utility::FormatString(_("Child PID: %i"), pid), M_INFO);
			pidFile.close();
		};


		// If we got a bad PID, fail out, if we got a good PID, we're all set.
		if (pid < 0) {
			exit(EXIT_FAILURE);
		}
		if (pid > 0) {
			// this kills the parent process
			exit(EXIT_SUCCESS);
		}

		// Past this point, we're working entirely with the child process
		// The parent process has been killed by the above exit().

		// Set the umask so we can access files written by the daemon
		umask(0);

		// Get a new SID
		pid_t sid;
		sid = setsid();

		// error out if the sid is < 0
		if (sid < 0) {
			Utility::LogMessage(_("Could not get valid SID for keycounter daemon, exiting."), M_ERROR);
			exit(EXIT_FAILURE);
		}

		// Change to the daemon's "home" directory 
		if ((chdir("/var/lib/linpulse/")) < 0) {
			Utility::LogMessage(_("Could not change to the daemon home directory, exiting."), M_ERROR);
			exit(EXIT_FAILURE);
		}

		// Close all the standard file descriptors
		close(STDIN_FILENO);
		close(STDOUT_FILENO);
		close(STDERR_FILENO);

		// log a message informing that we've finished daemonizing
		Utility::LogMessage(_("Finished daemonizing."), M_INFO);	
}
	
int main(int argc, char* argv[]) {
	
	// some handy variables for getopt_long
	int opt, longopt_index = 0;
	std::string eventDevice = "", outputFile = "", configFile = "";
	Config configuration;
	bool *daemonize = NULL;
	
	// set the locale for gnu gettext to know where to look
	// for localized messages
	setlocale (LC_MESSAGES, "");
  	textdomain ("keycounter");
  	bindtextdomain ("keycounter", LOCALEDIR);
	
	// Let's also set catches for SIGTERM and SIGINT--we just wanna log that we're terminating
	// there's nothing to actually clean up when terminated
	void (*prev_fn)(int);
	prev_fn = signal (SIGTERM,terminate);
  	if (prev_fn==SIG_IGN) signal(SIGTERM,SIG_IGN);
	prev_fn = signal (SIGINT,interrupt);
  	if (prev_fn==SIG_IGN) signal(SIGINT,SIG_IGN);
  	prev_fn = signal (SIGHUP,hangup);
  	if (prev_fn==SIG_IGN) signal(SIGHUP,SIG_IGN);
	
	// Let's parse the config file, and pull relevant settings out of it
	// We'll let them get overwritten by command-line settings as appropriate
	
	if (Utility::CheckPIDFile("/var/run/keycounter.pid")) {
		Utility::LogMessage(_("PID file exists, inside pid is valid, dying."), M_ERROR);
		cerr << _("Oops, it looks like there's already an instance running: /var/run/keycounter.pid") << endl;
		cerr << _("exists and the PID inside appears to be an existing process.") << endl;
		exit(EXIT_FAILURE);
	} else {
		Utility::LogMessage(_("PID file exists, inside PID is not valid -- keep on truckin'."), M_INFO);
	}

	// the option struct that getopt_long looks through to figure out which
	// short options to assign to a long option, and if options require paremeters
	static struct option long_options[] = {
		{"event-device", 1, NULL, 'e'},
		{"output-file", 1, NULL, 'o'},
		{"no-daemon", 0, NULL, 'n'},
		{"config-file", 1, NULL, 'c'},
		{"help", 0, NULL, 'h'},
		{"usage", 0, NULL, 'h'},
		{NULL, 0, NULL, 0} /* marks end-of-list */
	};

	// read in options via getopt_long until we don't have any more options
	// and either store the arguments or change the appropriate boolean flag
	while ((opt = getopt_long(argc, argv, "e:o:nc:h", long_options, &longopt_index)) > 0) {
		switch (opt) {
		case 'e':
			eventDevice = optarg;
			break;
		case 'o':
			outputFile = optarg;
			break;
		case 'n':
			*daemonize = false;
			break;
		case 'c':
			configFile = optarg;
			break;
		// if the option is -? or -h or --help, print the usage instructions
		case '?':
		case 'h':
		default:
			cerr << Utility::FormatString("usage: %s [options]\n", basename(argv[0]));
			cerr << _("  -e, --event-device ARG   The event device to poll when trying to read keystrokes"
				" (default: /dev/input/event0). \n");
			cerr << _("  -n, --no-daemon	Don't daemonize the keycounter (default: false). \n");
			cerr << _("  -o, --output-file ARG     The file to output keystroke statistics to "
				"(default: /var/keycounter). \n");
			cerr << _("  -v, --verbose	Output verbose, easily human-readable statistics to the output file "
				"(default: false). \n");
 			exit(EXIT_SUCCESS);
		}
	}
	
	if (configFile == "") {
		configuration.readFile(strdup(DEFAULT_CONFIG_FILE));
	} else {
		configuration.readFile(configFile.c_str());
	}
	
	if (eventDevice == "") {
		eventDevice = static_cast<std::string>(static_cast<const char*>(configuration.lookup("application.keycounter.eventdevice")));
	}
	
	if (outputFile == "") {
		outputFile = static_cast<std::string>(static_cast<const char*>(configuration.lookup("application.keycounter.outputfile")));
	}
	
	if (daemonize == NULL) {
		*daemonize = static_cast<bool>(configuration.lookup("application.system.daemonize"));
	}

	// Before we start, we'll output both to the log:
	Utility::LogMessage(Utility::FormatString(_("Event device path: %s"), eventDevice.c_str()), M_INFO);
	Utility::LogMessage(Utility::FormatString(_("Output file path: %s"), outputFile.c_str()), M_INFO);

	// Do we actually want to daemonize?
	if (daemonize) {
		Daemonize();
	} else {
		// we're not daemonizing, say so and keep working.
		Utility::LogMessage(Utility::FormatString(_("Not daemonizing, PID %i."), getpid()), M_INFO);
	}
	
	keycounter = new Keycounter(eventDevice, outputFile, configFile);
	commandInterface = new CommandInterface(keycounter);
	
	boost::thread keycounterThread(boost::bind(&LinPulse::Keycounter::Run, keycounter));
	boost::thread commandInterfaceThread(boost::bind(&LinPulse::CommandInterface::Run, commandInterface));
	
	// Let's drop our root privileges. We're gonna use the nobody user.
	struct passwd* pw = getpwnam("nobody");
	setuid(pw->pw_uid);
	setgid(pw->pw_gid);
	Utility::LogMessage(_("Dropped our root privileges, proceeding as 'nobody' user."), M_INFO);
	
	// wait for the thread to finish--this'll never happen, but that's okay.
	keycounterThread.join();
	commandInterfaceThread.join();
}
